/**
 * \file DTEAddIn.cpp
 *
 * \brief Implementation file for class CDTEAddIn
 *
 * $Id: DTEAddIn.cpp,v 1.1.1.1 2008/04/23 13:29:14 mgh Exp $
 *
 *
 * Copyright (C) 2006 Michael G. Herstine <sp1ff@pobox.com>
 *
 * Permission to use, copy, or modify this source code is hereby granted
 * free of  charge, provided  that this copyright  notice appear  on all
 * copies  and  on   all  source  code  derived  from   this  code.   No
 * representation is made regarding the suitability of this software for
 * any  purpose.  It  is provided  "as  is" without  express or  implied
 * warranty.
 *
 *
 */

#include "stdafx.h"               // Pre-compiled header
#include "AddIn.h"                // For CreateInstanceWithParamItf
#include "DTEAddIn.h"             // For class CDTEAddIn

#include "SampleCAIUI/resource.h" // Resource definitions for our
                                  // satellite DLL

_ATL_FUNC_INFO OnClickButtonInfo = {
  CC_STDCALL, VT_EMPTY, 2, { VT_DISPATCH, VT_BYREF | VT_BOOL }
};

////////////////////////////////////////////////////////////////////////
// Class CWindowEventsSink

CWindowEventsSink::CWindowEventsSink(CDTEAddIn *pParent) :
  m_pParent(pParent)
{ }

void __stdcall
CWindowEventsSink::WindowClosing(EnvDTE::Window * /*pWindow*/)
{
  ATLTRACE2(atlTraceHosting, 4, "CWindowEventSink::WindowClosing\n");
}

void __stdcall
CWindowEventsSink::WindowMoved(EnvDTE::Window * /*pWindow*/,
                                          long /*nTop*/,
                                          long /*nLeft*/,
                                          long /*nWidth*/,
                                          long /*nHeight*/)
{
  ATLTRACE2(atlTraceHosting, 4, "CWindowEventSink::WindowMoved\n");
}

void __stdcall
CWindowEventsSink::WindowActivated(
  EnvDTE::Window * /*pGotFocus*/,
  EnvDTE::Window * /*pLostFocus*/)
{
  ATLTRACE2(atlTraceHosting, 4, "CWindowEventSink::WindowActivated\n");
}

void __stdcall
CWindowEventsSink::WindowCreated(EnvDTE::Window * /*pWindow*/)
{
  ATLTRACE2(atlTraceHosting, 4, "CWindowEventsSink::WindowCreated\n");
}


////////////////////////////////////////////////////////////////////////
// Class CDTEAddIn

const wchar_t * CDTEAddIn::CMD_BAR_NAME = L"SampleCAI";

const CComBSTR CDTEAddIn::CMD_CONFIGURE(_T("Configure"));

const CComBSTR CDTEAddIn::CMD_DSC_CONFIGURE(
  _T("Configure the Sample AddIn"));

const wchar_t * CDTEAddIn::CMD_FULL_CONFIGURE =
  L"SampleCAI.CoAddIn.Configure";

const CComBSTR CDTEAddIn::CMD_SAYHELLO(_T("SayHello"));

const CComBSTR CDTEAddIn::CMD_DSC_SAYHELLO(
  _T("Say Hello"));

const wchar_t * CDTEAddIn::CMD_FULL_SAYHELLO =
  L"SampleCAI.CoAddIn.SayHello";

CDTEAddIn::CDTEAddIn() :
  m_nHost(Host_Unknown),
  m_objWindowEvents(this)
{
	m_hViaGui = NULL;
	m_hViaCommands = NULL;
}

HRESULT CDTEAddIn::FinalConstruct()
{
  ATLTRACE2(atlTraceCOM, 2, "CDTEAddIn::FinalConstruct\n");
  return S_OK;
}

void CDTEAddIn::FinalRelease()
{
  ATLTRACE2(atlTraceCOM, 2, "CDTEAddIn::FinalRelease\n");
}

void CDTEAddIn::SetParam(CAddIn *pParent)
{
  ATLASSERT(NULL != pParent);

  m_pParent = pParent;
}

////////////////////////////////////////////////////////////////////////
// Interface ISupportsErrorInfo

STDMETHODIMP CDTEAddIn::InterfaceSupportsErrorInfo(/*[in]*/ REFIID riid)
{
  static const IID* arr[] = {
    &EnvDTE::IID_IDTCommandTarget,
    &AddInDesignerObjects::IID__IDTExtensibility2,
  };

  for (int i = 0; i < sizeof(arr)/sizeof(arr[0]); ++i)
  {
    if (InlineIsEqualGUID(*arr[i],riid)) return S_OK;
  }

  return S_FALSE;
}

////////////////////////////////////////////////////////////////////////
// IDTCommandTarget

/**
 * \brief Returns the current status of the specified named command
 *
 *
 * \param bszCmdName The name of the command to check
 *
 * \param nNeededText A vsCommandStatusTextWanted constant specifying if
 * information is returned from the check, and if so, what type of
 * information is returned (Name, None, or Status)
 *
 * \param pnStatus A vsCommandStatus specifying the current status of
 * the command (Enabled, Invisible, Latched, Supported, or Unsupported)
 *
 * \param pvtCommandText The text to return if
 * vsCommandStatusTextWantedStatus is specified
 *
 *
 */

STDMETHODIMP CDTEAddIn::QueryStatus(
  BSTR                              bszCmdName,
  EnvDTE::vsCommandStatusTextWanted nNeededText,
  EnvDTE::vsCommandStatus          *pnStatusOption,
  VARIANT                          * /*pvtCommandText*/)
{
  const EnvDTE::vsCommandStatus STATUS_ON =
    (EnvDTE::vsCommandStatus)(EnvDTE::vsCommandStatusEnabled +
                              EnvDTE::vsCommandStatusSupported);

  if (EnvDTE::vsCommandStatusTextWantedNone == nNeededText)
  {
    if (!_wcsicmp(bszCmdName, CMD_FULL_CONFIGURE) ||
        !_wcsicmp(bszCmdName, CMD_FULL_SAYHELLO))
    {
      *pnStatusOption = STATUS_ON;
    }
  }

  return S_OK;
}

/**
 * \brief Execute a given named command
 *
 *
 * \param bszCmdName The name of the command to execute
 *
 * \param nExecuteOption A vsCommandExecOption constant specifying the
 * execution options
 *
 * \param pvtVariantIn A value passed to the command
 *
 * \param pvtVariantOut A value passed back to the invoker Exec method
 * after the command executes
 *
 * \param pvfHandled true indicates that the command was
 * implemented. false indicates that it was not
 *
 *
 */

STDMETHODIMP CDTEAddIn::Exec(
  BSTR                        bszCmdName,
  EnvDTE::vsCommandExecOption nExecuteOption,
  VARIANT                    * /*pvtVariantIn*/,
  VARIANT                    * /*pvtVariantOut*/,
  VARIANT_BOOL               *pvfHandled)
{
  *pvfHandled = VARIANT_FALSE;

  if (EnvDTE::vsCommandExecOptionDoDefault == nExecuteOption )
  {
    if (!_wcsicmp(bszCmdName, CMD_FULL_CONFIGURE))
    {
      ConfigureInternal();
      *pvfHandled = VARIANT_TRUE;
    }
    else if (!_wcsicmp(bszCmdName, CMD_FULL_SAYHELLO))
    {
		if (m_hViaGui)
			viaDialog(m_hViaGui);
      //m_pParent->SayHello();
      *pvfHandled = VARIANT_TRUE;
    }

  }

  return S_OK;
}

////////////////////////////////////////////////////////////////////////
// IDTExtensibility2

/**
 * \brief Called by our host to setup a connection to this AddIn
 *
 *
 * \param pApplication An IDispatch interface on the object representing
 * our host;  we'll need to  QI to find  out exactly what  interfaces it
 * supports
 *
 * \param nMode An enumerated  value indicating the circumstances of the
 * connection:
 *
 * <ol>
 *
 * <li>ext_cm_AfterStartup (0) Add-in was loaded after the application
 * started, or by setting the Connect property of the corresponding
 * AddIn object to True.
 *
 * <li>ext_cm_Startup (1) Add-in was loaded at startup.
 *
 * <li>ext_cm_External (2) Add-in was loaded externally by another
 * program or component.
 *
 * <li>ext_cm_CommandLine (3) Add-in was loaded through the Host's
 * command line.
 *
 * <li>ext_cm_Solution (4) The Add-in was loaded when a user loaded a
 * solution that required the Add-in.
 *
 * <li>ext_cm_UISetup (5) The Add-in was started for the first time
 * since being installed.
 *
 * </ol>
 *
 * \param pAddInInst An IDispatch reference on an AddIn instance
 * representing this AddIn
 *
 * \return S_OK on success, or a stock HRESULT else
 *
 *
 * This method will check to be sure that our commands are there (adding
 * them  if   they're  not).   Depending   on  the  value  of   nMode  &
 * SAMPLECAI_COMMAND_BAR_STYLE,  we  may  also  setup our  command  bars.
 * Finally, here is also where we sink any events we're interested in.
 *
 *
 */

STDMETHODIMP CDTEAddIn::OnConnection(IDispatch               *pApplication,
                                     AddInDO::ext_ConnectMode nMode,
                                     IDispatch               *pAddInInst,
                                     SAFEARRAY              ** /*ppCustom*/)
{
  HRESULT hr = S_OK;            // Eventual return value

  try
  {
    // Validate our parameters...
    if (NULL == pApplication) throw _com_error(E_INVALIDARG);
    if (NULL == pAddInInst)   throw _com_error(E_INVALIDARG);

    // take a reference on the AddIn object representing us,
    m_pAddIn = com_cast<EnvDTE::AddIn>(pAddInInst);

    // & try to figure out what DTE-compatible host we're currently
    // loaded into:
    m_nHost = GuessHostType(pApplication);

    ATLTRACE2(atlTraceHosting, 2, "CoDTEAddIn has been loaded with a c"
              "onnect mode of %d (our host is %d).\n", nMode, m_nHost);

    // Ok-- that done, we can actually carry on this method's actual work:
    AddCommands(nMode);  // adding our commands (and maybe toolbars),
    SinkEvents(nMode);   // & sink any events we care about.

    // That's it-- we're done...

  }
  HANDLE_COM_ERROR(CLSID_CoDTEAddIn, AddInDO::IID__IDTExtensibility2, hr)
  HANDLE_EXCEPTION(CLSID_CoDTEAddIn, AddInDO::IID__IDTExtensibility2, hr)

  return hr;

} // End CDTEAddIn::OnConnection.

/**
 * \brief Called by our host when this AddIn is un-loaded
 *
 *
 * \param nMode An ext_DisconnectMode enumeration value that informs an
 * add-in why it was unloaded
 *
 * \param ppCustom An empty array that you can use to pass host-specific
 * data for use after the add-in unloads
 *
 *
 * This method un-advise any event  sinks we've setup.  Depending on the
 * value of nMode &  SAMPLECAI_COMMAND_BAR_STYLE, we may also remove our
 * command bars.
 *
 *
 */

STDMETHODIMP CDTEAddIn::OnDisconnection(AddInDO::ext_DisconnectMode nMode,
                                        SAFEARRAY           ** /*ppCustom*/)
{
  HRESULT hr = S_OK;            // Eventual return value

  try
  {
    ATLTRACE2(atlTraceHosting, 2, "CoDTEAddIn is being unloaded with"
              " a removal code of %d.\n", nMode);

	if (m_hViaGui)
		viaRelease(&m_hViaGui);
	if (m_hViaCommands)
		viaCommandsRelease(&m_hViaCommands);

    // Cleanup in the reverse order:
    UnSinkEvents(nMode);        // Un-advise any event sinks we've setup,
    RemoveCommands(nMode);      // & cleanup our command bars.

    // That done, we just Release any outstanding references we may have
    // on our host (or host-provided objects):
    m_pAddIn = NULL;
    m_pApp   = NULL;
    m_pExcel = NULL;

  }
  HANDLE_COM_ERROR(CLSID_CoDTEAddIn, AddInDO::IID__IDTExtensibility2, hr)
  HANDLE_EXCEPTION(CLSID_CoDTEAddIn, AddInDO::IID__IDTExtensibility2, hr)

  return hr;

} // End CDTEAddIn::OnDisconnection.

STDMETHODIMP CDTEAddIn::OnAddInsUpdate(SAFEARRAY ** /*custom*/)
{
  return S_OK;
}

STDMETHODIMP CDTEAddIn::OnStartupComplete(SAFEARRAY ** /*custom*/)
{
  return S_OK;
}

STDMETHODIMP CDTEAddIn::OnBeginShutdown(SAFEARRAY ** /*custom*/)
{
  return S_OK;
}

void /*__stdcall*/ CDTEAddIn::OnClickCfg(
  IDispatch * /*Office::_CommandBarButton**/ /*pCtrl*/,
  VARIANT_BOOL * /*pCancelDefault*/)
{
  ConfigureInternal();
}

void /*__stdcall*/ CDTEAddIn::OnClickSay(
  IDispatch * /*Office::_CommandBarButton**/ /*pCtrl*/,
  VARIANT_BOOL * /*pCancelDefault*/)
{
  m_pParent->SayHello();
}

////////////////////////////////////////////////////////////////////////

/**
 * \brief Make sure our commands are present & setup any needed toolbars
 *
 * \sa OnConnection
 *
 *
 * \param nMode Enumerated value indicating the nature of this
 * connection
 *
 *
 * This method is only called from OnConnection; I just factored it out
 * to make that method more readable.
 *
 *
 */

void CDTEAddIn::AddCommands(AddInDO::ext_ConnectMode nMode)
{
    switch (m_nHost)
    {
    case Host_VS2003:
      AddCommandsVS2003(nMode);
      break;
    case Host_VS2005:
      AddCommandsVS2005(nMode);
      break;
    case Host_Excel2003:
      AddCommandsExcel2003(nMode);
      break;
    default:
      ATLTRACE2(atlTraceHosting, 0, "CoDTEAddIn does not (yet) support"
                " host %d!\n", m_nHost);
      break;
    } // End switch on host application.

} // End CDTEAddIn::AddCommands.

/**
 * \brief Add our commands to Excel 2003
 *
 * \sa OnConnection, AddCommands
 *
 *
 * \param nMode Reason the host is connecting to us
 *
 * \pre m_nHost is Host_Excel2003
 *
 *
 */

void CDTEAddIn::AddCommandsExcel2003(AddInDO::ext_ConnectMode nMode)
{
  HRESULT hr = S_OK;
  CComPtr<EnvDTE::Commands> pCmds;
  CComPtr<IDispatch> pDisp, pDisp1, pDisp2;
  CComPtr<EnvDTE::Command> pCmdCfg, pCmdSay;

  ATLTRACE2(atlTraceHosting, 4, "CDTEAddIn::AddCommandsExcel2003: %d\n",
            nMode);

  ATLASSERT(Host_Excel2003 == m_nHost);

  if (AddInDO::ext_cm_AfterStartup == nMode || // Loaded after app startup
      AddInDO::ext_cm_Startup      == nMode)   // Loaded on app startup
  {
    ATLTRACE(atlTraceHosting, 2, "Creating a temporary command bar.\n");

    // We'll create a temporary command bar.  Visual Studio 2003 uses
    // the Office Command Bars model.  Specifically, we'll need
    // Office::_CommandBars interface.  We can get one by asking our
    // host...
    CComQIPtr<Office::_CommandBars> pBars;
    hr = m_pExcel->get_CommandBars(&pBars);
    if (FAILED(hr))
    {
      ATLTRACE2(atlTraceHosting, 0, "WARNING: _Application::get_Comma"
                "ndBars failed with HRESULT 0x%08x! Skipping command "
                "bar creation.\n", hr);
      return;
    }

    CComVariant vtCmdBarName(CMD_BAR_NAME);

    // At this point, we can add the Command Bar.  First, however, let's
    // be paranoid & see if it already exists:
    CComQIPtr<Office::CommandBar> pBar; // Will hold a reference on our
                                        // command bar, one way or
                                        // the other...
    // Just like our commands, we call the 'Item' method,
    hr = pBars->get_Item(vtCmdBarName, &pBar);
    // which will return E_INALIDARG if no such item exists:
    if (E_INVALIDARG == hr)
    {
      hr = pBars->Add(
        vtCmdBarName,                         // Name
        CComVariant((LONG)Office::msoBarTop), // Position
        CComVariant(),                        // Parent
        CComVariant(VARIANT_TRUE),            // Temporary
        &m_pCommandBar);                      // [out] param
      if (FAILED(hr)) throw _com_error(hr);

      CComQIPtr<Office::CommandBarControls> pCtls;
      hr = m_pCommandBar->get_Controls(&pCtls);
      if (FAILED(hr)) throw _com_error(hr);

      CComVariant vtButton((LONG)Office::msoControlButton);
      CComVariant vtEmpty(DISP_E_PARAMNOTFOUND, VT_ERROR);
      CComVariant vtTrue(TRUE);

      CComQIPtr<Office::CommandBarControl> pCtl;
      hr = pCtls->Add(vtButton,      // Control type
                      vtEmpty,       // No ID
                      vtEmpty,       // No Parameter
                      vtEmpty,       // Add at the end of the bar
                      vtTrue,        // Temporary
                      &pCtl);        // [out] parameter
      if (FAILED(hr)) throw _com_error(hr);

      m_pCfg = pCtl;

      HBITMAP hBmp = (HBITMAP)::LoadImage(
        _pModule->GetResourceInstance(),
        MAKEINTRESOURCE(IDB_CFG32),
        IMAGE_BITMAP,0,0,LR_LOADMAP3DCOLORS);

      ::OpenClipboard(NULL);
      ::EmptyClipboard();
      ::SetClipboardData(CF_BITMAP, (HANDLE)hBmp);
      ::CloseClipboard();
      ::DeleteObject(hBmp);

      hr = m_pCfg->put_Style(Office::msoButtonIconAndCaption);
      hr = m_pCfg->PasteFace();

      hr = CfgEvents::DispEventAdvise((IDispatch*)m_pCfg);

      pCtl = (Office::CommandBarControl*)NULL;
      hr = pCtls->Add(vtButton,      // Control type
                      vtEmpty,       // No ID
                      vtEmpty,       // No Parameter
                      vtEmpty,       // Add at the end of the bar
                      vtTrue,        // Temporary
                      &pCtl);        // [out] parameter
      if (FAILED(hr)) throw _com_error(hr);

      m_pSay = pCtl;

      hBmp = (HBITMAP)::LoadImage(
        _pModule->GetResourceInstance(),
        MAKEINTRESOURCE(IDB_SAY32),
        IMAGE_BITMAP,0,0,LR_LOADMAP3DCOLORS);

      ::OpenClipboard(NULL);
      ::EmptyClipboard();
      ::SetClipboardData(CF_BITMAP, (HANDLE)hBmp);
      ::CloseClipboard();
      ::DeleteObject(hBmp);

      hr = m_pSay->put_Style(Office::msoButtonIconAndCaption);
      hr = m_pSay->PasteFace();

      hr = SayEvents::DispEventAdvise((IDispatch*)m_pSay);

      m_pCommandBar->put_Visible(VARIANT_TRUE);

    } // End if on non-existent command bar.

    if (FAILED(hr)) throw _com_error(hr);

  } // End if on connect mode

} // End CDTEAddIn::AddCommandsExcel2003.

/**
 * \brief Add our commands to Visual Studio .Net 2003
 *
 * \sa OnConnection, AddCommands, cmdbar_defns
 *
 *
 * \param nMode Reason the host is connecting to us
 *
 * \throw _com_error On non-recoverable COM method failure
 *
 * \pre m_nHost is Host_VS2003
 *
 *
 * Here is where  we add commands & command bars  to Visual Studio 2003.
 * Each time this method is invoked,  it will check the host app for our
 * commands & add them, if they're not there.
 *
 * It may  also add a command  bar-- see \ref cmdbar_defns  "here" for a
 * discussion of how command bars are handled.
 *
 *
 */

void CDTEAddIn::AddCommandsVS2003(AddInDO::ext_ConnectMode nMode)
{
  HRESULT hr = S_OK;
  CComPtr<EnvDTE::Commands> pCmds;
  CComPtr<IDispatch> pDisp, pDisp1, pDisp2;
  CComPtr<EnvDTE::Command> pCmdCfg, pCmdSay;

  ATLTRACE2(atlTraceHosting, 4, "CDTEAddIn::AddCommandsVS2003: %d\n",
            nMode);

  ATLASSERT(Host_VS2003 == m_nHost);

  // Ok-- the first thing we do is check to see if our commands are
  // defined.  If they're not, we define 'em, no matter what 'nMode'
  // is.  We can find that out through the EnvDTE::Commands interface.
  // We can get that interface from our host:

  if (FAILED(hr = m_pApp->get_Commands(&pCmds))) throw _com_error(hr);

  // We can check to see if a given command exists by calling the 'Item'
  // method on the Commands interface with the fully-qualified command
  // name (e.g. "SampleCAI.CoAddIn.Configure").

  hr = pCmds->Item(CComVariant(CMD_FULL_CONFIGURE), -1L, &pCmdCfg);

  // If there is no such command, 'Item' will return E_INVALIDARG:

  if (E_INVALIDARG == hr)
  {
    // It's not; this could be because it's the first time we've been
    // loaded, or because the command was removed for some reason.
    // Either way, add it now:
    hr = pCmds->AddNamedCommand(
      m_pAddIn,                           // Us-- this AddIn
      CMD_CONFIGURE,                      // Name
      CMD_CONFIGURE,                      // Button text
      CMD_DSC_CONFIGURE,                  // Tooltip
      VARIANT_FALSE,                      // *Not* an MSO button
      IDB_CFG16,                          // Satellite DLL res ID
      NULL,                               // Context GUIDs
      EnvDTE::vsCommandStatusSupported +  // Disabled flags
      EnvDTE::vsCommandStatusEnabled,
      &pCmdCfg);                          // [out] param
  } // End if on no Configure command.

  if (FAILED(hr)) throw _com_error(hr);

  // Ok, now do the same for the "SayHello" command:
  hr = pCmds->Item(CComVariant(CMD_FULL_SAYHELLO), -1L, &pCmdSay);
  if (E_INVALIDARG == hr)
  {
    hr = pCmds->AddNamedCommand(
      m_pAddIn,                           // Us-- the AddIn
      CMD_SAYHELLO,                       // Name
      CMD_SAYHELLO,                       // Button text
      CMD_DSC_SAYHELLO,                   // Tooltip
      VARIANT_FALSE,                      // *Not* an MSO button
      IDB_SAY16,                          // Satellite DLL res ID
      NULL,                               // Context GUIDs
      EnvDTE::vsCommandStatusSupported +  // Disabled flags
      EnvDTE::vsCommandStatusEnabled,
      &pCmdSay);                          // [out] param
  } // End if on no SayHello command.

  if (FAILED(hr)) throw _com_error(hr);

  // At this point, we know that our commands are there. We next deal
  // with our command bars...

# if ( SAMPLECAI_COMMAND_BAR_STYLE == SAMPLECAI_COMMAND_BAR_TEMPORARY )

  // We're using temporary command bars.  In this case, we ignore the
  // one time only "UISetup" invocation; we only create our command bar
  // when the AddIn is loaded normally:

  if (AddInDO::ext_cm_AfterStartup == nMode || // Loaded after app startup
      AddInDO::ext_cm_Startup      == nMode)   // Loaded on app startup
  {
    ATLTRACE(atlTraceHosting, 2, "Creating a temporary command bar.\n");

    // We'll create a temporary command bar.  Visual Studio 2003 uses
    // the Office Command Bars model.  Specifically, we'll need
    // Office::_CommandBars interface.  We can get one by asking our
    // host...
    hr = m_pApp->get_CommandBars(&pDisp);
    if (FAILED(hr))
    {
      ATLTRACE2(atlTraceHosting, 0, "WARNING: _DTE::get_CommandBars fa"
                "iled with HRESULT 0x%08x! Skipping command bar creati"
                "on.\n", hr);
      return;
    }

    // & QI'ing for the interface we want:
    CComQIPtr<Office::_CommandBars> pBars = pDisp;
    if (NULL == pDisp) throw _com_error(hr); // Something's *really* wrong

    CComVariant vtCmdBarName(CMD_BAR_NAME);

    // At this point, we can add the Command Bar.  First, however, let's
    // be paranoid & see if it already exists:
    CComQIPtr<Office::CommandBar> pBar; // Will hold a reference on our
                                        // command bar, one way or
                                        // the other...
    // Just like our commands, we call the 'Item' method,
    hr = pBars->get_Item(vtCmdBarName, &pBar);
    // which will return E_INALIDARG if no such item exists:
    if (E_INVALIDARG == hr)
    {
      hr = pBars->Add(
        vtCmdBarName,                         // Name
        CComVariant((LONG)Office::msoBarTop), // Position
        CComVariant(),                        // Parent
        CComVariant(VARIANT_TRUE),            // Temporary
        &m_pCommandBar);                      // [out] param
      if (FAILED(hr)) throw _com_error(hr);

      // If we're here, we have a brand new Command Bar.  Now we need to
      // add some controls to it; specifically, buttons invoking our
      // commands.
      hr = pCmdCfg->AddControl(m_pCommandBar, 1L, &pDisp1);
      if (FAILED(hr)) throw _com_error(hr);

      hr = pCmdSay->AddControl(m_pCommandBar, 2L, &pDisp2);
      if (FAILED(hr)) throw _com_error(hr);

      m_pCommandBar->put_Visible(VARIANT_TRUE);

    } // End if on non-existent command bar.

    if (FAILED(hr)) throw _com_error(hr);

  } // End if on connect mode

# else // SAMPLECAI_COMMAND_BAR_STYLE == SAMPLECAI_COMMAND_BAR_PERMANENT ||
       // SAMPLECAI_COMMAND_BAR_STYLE == SAMPLECAI_COMMAND_BAR_SEMIPERMANENT

  // Ok-- we're using permanent command bars.  In this scheme, we'll
  // create them once, when we get the one-time-only startup code
  // "UISetup":
  if (5/*AddInDO::ext_cm_UISetup*/ == nMode)
  {
    pDisp = NULL;
    CComBSTR bstrCmdBarName(CMD_BAR_NAME);

    // Note that this time, we're calling EnvDTE::Commands::AddCommandBar,
    // rather than Office::_CommandBars->Add.
    hr = pCmds->AddCommandBar(
      bstrCmdBarName,                  // Name
      EnvDTE::vsCommandBarTypeToolbar, // Type
      NULL,                            // Parent
      0L,                              // Position
      &pDisp);                         // [out] parameter
    if (FAILED(hr)) throw _com_error(hr);

    // This is irritating: the new Command Bar reference comes back as
    // an IDispatch, but to do anything usefull, we have to QI to
    // Office::CommandBar.
    CComQIPtr<Office::CommandBar> pBar = pDisp;
    if (NULL == pBar) throw _com_error(hr);

    // Ok-- now we've got an Office::CommandBar reference on a new
    // OCommand Bar, just like in the temporary case.  Just the same, we
    // Oneed to add buttons:
    hr = pCmdCfg->AddControl(pBar, 1L, &pDisp1);
    if (FAILED(hr)) throw _com_error(hr);
    hr = pCmdSay->AddControl(pBar, 2L, &pDisp2);
    if (FAILED(hr)) throw _com_error(hr);

    pBar->put_Visible(VARIANT_TRUE);
  }

# endif // SAMPLECAI_COMMAND_BAR_STYLE

} // End AddCommandsVS2003.

/**
 * \brief Add our commands to Visual Studio 2005
 *
 * \sa OnConnection, AddCommands, cmdbar_defns
 *
 *
 * \param nMode Reason the host is connecting to us
 *
 * \throw _com_error On non-recoverable COM method failure
 *
 * \pre m_nHost is Host_VS2005
 *
 *
 * Here is where  we add commands & command bars  to Visual Studio 2005.
 * Each time this method is invoked,  it will check the host app for our
 * commands & add them, if they're not there.
 *
 * It may  also add a command  bar-- see \ref cmdbar_defns  "here" for a
 * discussion of how command bars are handled.
 *
 *
 */

void CDTEAddIn::AddCommandsVS2005(AddInDO::ext_ConnectMode nMode)
{
  HRESULT hr = S_OK;
  CComPtr<IDispatch> pDisp, pDisp1, pDisp2;
  CComPtr<EnvDTE::Command> pCmdCfg, pCmdSay;

  ATLTRACE2(atlTraceHosting, 4, "CDTEAddIn::AddCommandsVS2005: %d\n",
            nMode);

  ATLASSERT(Host_VS2005 == m_nHost);

  // Ok-- the first thing we do is check to see if our commands are
  // defined.  If they're not, we define 'em, no matter what 'nMode' is.
  // Now, EnvDTE::get_Commands still returns an EnvDTE::Commands
  // interface, but I see that VS 2005 introduced a *new* interface,
  // EnvDTE80::Commands.  I'm not sure which one to use, but better to
  // use the latest & greatest, I guess:

  CComPtr<EnvDTE::Commands> pCmds;
  if (FAILED(hr = m_pApp->get_Commands(&pCmds))) throw _com_error(hr);

  CComQIPtr<EnvDTE80::Commands2> pCmds80 = pCmds;
  if (NULL == pCmds80) throw _com_error(E_NOINTERFACE);

  // We can check to see if a given command exists by calling the 'Item'
  // method on the Commands interface with the fully-qualified command
  // name (e.g. "SampleCAI.CoAddIn.Configure").

  hr = pCmds80->Item(CComVariant(CMD_FULL_CONFIGURE),-1L, &pCmdCfg);

  // If there is no such command, 'Item' will return E_INVALIDARG:

  if (E_INVALIDARG == hr)
  {
    // It's not; this could be because it's the first time we've been
    // loaded, or because the command was removed for some reason.
    // Either way, add it now:
    hr = pCmds80->AddNamedCommand2(
      m_pAddIn,                            // Us-- the AddIn
      CMD_CONFIGURE,                       // Name
      CMD_CONFIGURE,                       // Button text
      CMD_DSC_CONFIGURE,                   // Tooltip
      VARIANT_FALSE,                       // *Not* an MSO button
      CComVariant(IDB_CFG16),              // Satellite DLL res ID
      NULL,                                // Context GUIDs
      EnvDTE::vsCommandStatusSupported +   // Disabled flags
      EnvDTE::vsCommandStatusEnabled,
      EnvDTE80::vsCommandStylePictAndText, // Style flags
      EnvDTE80::vsCommandControlTypeButton,// Control Type
      &pCmdCfg);                           // [out] param
  } // End if on no Configure command.

  if (FAILED(hr)) throw _com_error(hr);

  // Ok, now do the same for the "SayHello" command:
  hr = pCmds80->Item(CComVariant(CMD_FULL_SAYHELLO), -1L, &pCmdSay);
  if (E_INVALIDARG == hr)
  {
    hr = pCmds80->AddNamedCommand2(
      m_pAddIn,                            // Us-- the AddIn
      CMD_SAYHELLO,                        // Name
      CMD_SAYHELLO,                        // Button Text
      CMD_DSC_SAYHELLO,                    // Tooltip
      VARIANT_FALSE,                       // *Not* an MSO button
      CComVariant(IDB_SAY16),              // Satellite DLL res ID
      NULL,                                // Context GUIDs
      EnvDTE::vsCommandStatusSupported +   // Disabled flags
      EnvDTE::vsCommandStatusEnabled,
      EnvDTE80::vsCommandStylePictAndText, // Style flags
      EnvDTE80::vsCommandControlTypeButton,// Control type
      &pCmdSay);                           // [out] param
  } // End if on no "SayHello" command.

  if (FAILED(hr)) throw _com_error(hr);

  // At this point, we know that our commands are there. We next deal
  // with our command bars...

# if ( SAMPLECAI_COMMAND_BAR_STYLE == SAMPLECAI_COMMAND_BAR_TEMPORARY )

  // We're using temporary command bars.  In this case, we ignore the
  // one time only "UISetup" invocation; we only create our command bar
  // when the AddIn is loaded normally:

  if (AddInDO::ext_cm_AfterStartup == nMode || // Loaded after app startup
      AddInDO::ext_cm_Startup      == nMode)   // Loaded on app startup
  {
    ATLTRACE(atlTraceHosting, 2, "Creating a temporary command bar.\n");

    // We'll create a temporary command bar.  Just like Visual Studio
    // 2003, we begin by asking the app for it's Command Bars
    // collection:
    hr = m_pApp->get_CommandBars(&pDisp);
    if (FAILED(hr))
    {
      ATLTRACE2(atlTraceHosting, 0, "WARNING: _DTE::get_CommandBars fa"
                "iled with HRESULT 0x%08x! Skipping command bar creati"
                "on.\n", hr);
      return;
    }

    // Visual Studio 2005 introduced it's own set of new Command Bar
    // interfaces.  Again, I'm not sure if we can just use the old ones.
    CComQIPtr<MSVSCBs::_CommandBars> pBars = pDisp;
    if (NULL == pDisp) throw _com_error(E_NOINTERFACE);

    CComVariant vtCmdBarName(CMD_BAR_NAME);

    // At this point, we can add the Command Bar.  First, however, let's
    // be paranoid & see if it already exists:
    CComQIPtr<MSVSCBs::CommandBar> pBar;
    // Just like our commands, we call the 'Item' method,
    hr = pBars->get_Item(vtCmdBarName, &pBar);
    // which will return E_INALIDARG if no such item exists:
    if (E_INVALIDARG == hr)
    {
      CComVariant vtPosition((LONG)MsoBarPosition::msoBarFloat);
      CComVariant vtEmpty(DISP_E_PARAMNOTFOUND, VT_ERROR);

      hr = pBars->Add(vtCmdBarName,               // Name
                      vtPosition,                 // Position
                      CComVariant(VARIANT_FALSE), // Menu bar
                      CComVariant(VARIANT_TRUE),  // Temporary
                      &m_pCommandBar80);          // [out] param
      if (FAILED(hr)) throw _com_error(hr);

      // If we're here, we have a brand new Command Bar.  Now we need to
      // add some controls to it; specifically, buttons invoking our
      // commands.
      hr = pCmdCfg->AddControl(m_pCommandBar80, 1L, &pDisp1);
      if (FAILED(hr)) throw _com_error(hr);
      hr = pCmdSay->AddControl(m_pCommandBar80, 2L, &pDisp2);
      if (FAILED(hr)) throw _com_error(hr);

      m_pCommandBar80->put_Visible(VARIANT_TRUE);

    } // End if on non-existent command bar.

    if (FAILED(hr)) throw _com_error(hr);

  } // End if on connect mode

# else // SAMPLECAI_COMMAND_BAR_STYLE == SAMPLECAI_COMMAND_BAR_PERMANENT ||
       // SAMPLECAI_COMMAND_BAR_STYLE == SAMPLECAI_COMMAND_BAR_SEMIPERMANENT

  // If this is the first time the AddIn's being loaded, create a
  // Toolbar, as well.
  if (5/*AddInDO::ext_cm_UISetup*/ == nMode)
  {
    CComBSTR bstrCmdBarName(CMD_BAR_NAME);
    pDisp = NULL;
    hr = pCmds80->AddCommandBar(bstrCmdBarName,
                               EnvDTE::vsCommandBarTypeToolbar,
                               NULL,
                               1L/*MSVSCBs::MsoBarPosition::msoBarTop*/,
                               &pDisp);
    if (FAILED(hr)) throw _com_error(hr);

    CComQIPtr<MSVSCBs::CommandBar> pBar = pDisp;
    if (NULL == pBar) throw _com_error(hr);

    hr = pCmdCfg->AddControl(pBar, 1L, &pDisp1);
    if (FAILED(hr)) throw _com_error(hr);
    hr = pCmdSay->AddControl(pBar, 2L, &pDisp2);
    if (FAILED(hr)) throw _com_error(hr);

    pBar->put_Visible(VARIANT_TRUE);
  }

# endif // SAMPLECAI_COMMAND_BAR_STYLE

} // End AddCommandsVS2005.

void CDTEAddIn::ConfigureInternal()
{
  ATLASSERT(Host_Unknown != m_nHost);

  switch (m_nHost)
  {
  case Host_VS2003:
  case Host_VS2005:
  {
    HRESULT hr = S_OK;
    hr = m_pApp->ExecuteCommand(CComBSTR("Tools.Options"), CComBSTR(""));
    hr;
    break;
  }
  case Host_Excel2003:
    m_pParent->Configure();
    break;
  default:                      // Should never be here...
    ATLASSERT(false);
    throw std::logic_error("You didn't keep CDTEAddIn::ConfigureInter"
                           "nal up-to-date with the Host enumeration!");
  }

} // End CDTEAddIn::ConfigureInternal.

CDTEAddIn::Host CDTEAddIn::GuessHostType(IDispatch *pApp)
{
  HRESULT hr = S_OK;

  // Are we being hosted by Visual Studio 2005?  I suspect this will be
  // the most common case.  Check by asking for an ENVDTE80::DTE2
  // interface...
  EnvDTE80::DTE2 *pDTE2Raw;
  hr = pApp->QueryInterface(EnvDTE80::IID_DTE2, (void**)&pDTE2Raw);
  if (SUCCEEDED(hr))
  {
    m_pApp = com_cast<EnvDTE::_DTE>(pApp);
	m_hViaCommands = viaCommandsCreate(pApp, VIA_VS_2005);
	if (m_hViaCommands)
		m_hViaGui = viaCreate(GetModuleHandle(NULL), m_hViaCommands);
    pDTE2Raw->Release();

    return Host_VS2005;
  }

  // Ok-- maybe it's Visual Studio 2003...
  EnvDTE::_DTE *pDTERaw;
  hr = pApp->QueryInterface(EnvDTE::IID__DTE, (void**)&pDTERaw);
  if (SUCCEEDED(hr))
  {
    m_pApp = pDTERaw;
	m_hViaCommands = viaCommandsCreate(pApp, VIA_VS_2003);
	if (m_hViaCommands)
		m_hViaGui = viaCreate(GetModuleHandle(NULL), m_hViaCommands);
    return Host_VS2003;
  }

#if 0
  // Maybe Excel?
  Excel::_Application *pExcelRaw;
  hr = pApp->QueryInterface(Excel::IID__Application, (void**)&pExcelRaw);
  if (SUCCEEDED(hr))
  {
    m_pExcel = pExcelRaw;
    return Host_Excel2003;
  }
#endif

  return Host_Unknown;

} // End GuessHost.

void CDTEAddIn::RemoveCommands(AddInDO::ext_DisconnectMode nMode)
{
  switch (m_nHost)
  {
  case Host_VS2003:
    RemoveCommandsVS2003(nMode);
    break;
  case Host_VS2005:
    RemoveCommandsVS2005(nMode);
    break;
  case Host_Excel2003:
    RemoveCommandsExcel2003(nMode);
    break;
  default:
    ATLTRACE2(atlTraceHosting, 0, "I don't support this host!\n");
  } // End switch on host application.
}

/**
 * \brief Remove our commands from Excel 2003
 *
 *
 * \param nMode Reason indicating why we're being unloaded
 *
 *
 */

void CDTEAddIn::RemoveCommandsExcel2003(AddInDO::ext_DisconnectMode nMode)
{
  ATLTRACE2(atlTraceHosting, 4, "CDTEAddIn::RemoveCommandsExcel2003: %d\n",
            nMode);
  ATLASSERT(Host_Excel2003 == m_nHost);

  nMode; // Shutup the compiler

  CfgEvents::DispEventUnadvise( (IDispatch*)m_pCfg );
  SayEvents::DispEventUnadvise( (IDispatch*)m_pCfg );

} // End DTEAddIn::RemoveCommandsExcel2003.

/**
 * \brief Remove our commands from Visual Studio .Net 2003
 *
 *
 * \param nMode Reason indicating why we're being unloaded
 *
 *
 */

void CDTEAddIn::RemoveCommandsVS2003(AddInDO::ext_DisconnectMode nMode)
{
# if ( SAMPLECAI_COMMAND_BAR_STYLE == SAMPLECAI_COMMAND_BAR_TEMPORARY )

  // Temporary command bars are always deleted on unload...
  nMode; // Shutup the compiler
  if (NULL != m_pCommandBar)
  {
    HRESULT hr = m_pCommandBar->Delete();
    if (FAILED(hr))
    {
      ATLTRACE2(atlTraceHosting, 0, "WARNING: Failed to delete the Sampl"
                "eCAI (temporary) command bar!\n");
    }
  }

# elif ( SAMPLECAI_COMMAND_BAR_STYLE == SAMPLECAI_COMMAND_BAR_PERMANENT )

  // Do nothing -- the commands & command bar will be removed
  // independently at un-install time.
  nMode; // Shutup the compiler

# elif (SAMPLECAI_COMMAND_BAR_STYLE == SAMPLECAI_COMMAND_BAR_SEMIPERMANENT)

  // If the user manually unloaded us, remove the command bar as well as
  // our commands:
  if (AddInDO::ext_dm_UserClosed == nMode)
  {
    HRESULT hr = S_OK;

    CComPtr<EnvDTE::Commands> pCmds;
    if (FAILED(hr = m_pApp->get_Commands(&pCmds))) throw _com_error(hr);

    // First, remove our toolbar (if it's there):
    CComPtr<IDispatch> pDisp;
    if (FAILED(hr = m_pApp->get_CommandBars(&pDisp))) throw _com_error(hr);

    CComQIPtr<Office::_CommandBars> pBars = pDisp;

    CComPtr<Office::CommandBar> pBar;
    hr = pBars->get_Item(CComVariant(CMD_BAR_NAME), &pBar);
    if (SUCCEEDED(hr))
    {
      hr = pCmds->RemoveCommandBar(pBar);
      if (FAILED(hr))
      {
        ATLTRACE2(atlTraceHosting, 0, "WARNING: Failed to delete the S"
                  "ampleCAI Command Bar.\n");
      }
    }

    // Now, remove our named commands:
    CComPtr<EnvDTE::Command> pCmd;
    hr = pCmds->Item(CComVariant(CMD_FULL_CONFIGURE), -1L, &pCmd);
    if (SUCCEEDED(hr))
    {
      hr = pCmd->Delete();
      if (FAILED(hr))
      {
        ATLTRACE2(atlTraceHosting, 1, "WARNING: Failed to delete the Con"
                  "figure command.\n");
      }
    }

    pCmd = NULL;
    hr = pCmds->Item(CComVariant(CMD_FULL_SAYHELLO), -1L, &pCmd);
    if (SUCCEEDED(hr))
    {
      hr = pCmd->Delete();
      if (FAILED(hr))
      {
        ATLTRACE2(atlTraceHosting, 1, "WARNING: Failed to delete the Say"
                  " Hello command.\n");
      }
    }
  }

# endif // SAMPLECAI_COMMAND_BAR_STYLE

} // End RemoveCommandsVS2003.

/**
 * \brief Remove our commands from Visual Studio .Net 2005
 *
 *
 * \param nMode Reason indicating why we're being unloaded
 *
 *
 */

void CDTEAddIn::RemoveCommandsVS2005(AddInDO::ext_DisconnectMode nMode)
{
# if ( SAMPLECAI_COMMAND_BAR_STYLE == SAMPLECAI_COMMAND_BAR_TEMPORARY )

  // Temporary command bars are always deleted on unload...
  nMode; // Shutup the compiler
  if (NULL != m_pCommandBar80)
  {
    HRESULT hr = m_pCommandBar80->Delete();
    if (FAILED(hr))
    {
      ATLTRACE2(atlTraceHosting, 0, "WARNING: Failed to delete the Sampl"
                "eCAI (temporary) command bar!\n");
    }
  }

# elif ( SAMPLECAI_COMMAND_BAR_STYLE == SAMPLECAI_COMMAND_BAR_PERMANENT )

  // Do nothing -- the commands & command bar will be removed
  // independently at un-install time.
  nMode; // Shutup the compiler

# elif (SAMPLECAI_COMMAND_BAR_STYLE == SAMPLECAI_COMMAND_BAR_SEMIPERMANENT)

  if (AddInDO::ext_dm_UserClosed == nMode)
  {
    HRESULT hr = S_OK;

    CComPtr<EnvDTE::Commands> pCmds;
    if (FAILED(hr = m_pApp->get_Commands(&pCmds))) throw _com_error(hr);

    CComQIPtr<EnvDTE80::Commands2> pCmds2 = pCmds;

    // First, remove our toolbar (if it's there):
    CComPtr<IDispatch> pDisp;
    if (FAILED(hr = m_pApp->get_CommandBars(&pDisp))) throw _com_error(hr);

    CComQIPtr<MSVSCBs::_CommandBars> pBars = pDisp;
    if (NULL == pBars) throw _com_error(hr);

    CComPtr<MSVSCBs::CommandBar> pBar;
    hr = pBars->get_Item(CComVariant(CMD_BAR_NAME), &pBar);
    if (SUCCEEDED(hr))
    {
      hr = pCmds2->RemoveCommandBar(pBar);
      if (FAILED(hr))
      {
        ATLTRACE2(atlTraceHosting, 0, "WARNING: Failed to delete the S"
                  "ampleCAI Command Bar.\n");
      }
    }

    // Now, remove our named commands:
    CComPtr<EnvDTE::Command> pCmd;
    hr = pCmds2->Item(CComVariant(CMD_FULL_CONFIGURE), -1L, &pCmd);
    if (SUCCEEDED(hr))
    {
      hr = pCmd->Delete();
      if (FAILED(hr))
      {
        ATLTRACE2(atlTraceHosting, 1, "WARNING: Failed to delete the Con"
                  "figure command.\n");
      }
    }

    pCmd = NULL;
    hr = pCmds2->Item(CComVariant(CMD_FULL_SAYHELLO), -1L, &pCmd);
    if (SUCCEEDED(hr))
    {
      hr = pCmd->Delete();
      if (FAILED(hr))
      {
        ATLTRACE2(atlTraceHosting, 1, "WARNING: Failed to delete the Say"
                  " Hello command.\n");
      }
    }
  }

# endif // SAMPLECAI_COMMAND_BAR_STYLE

} // End RemoveCommandsVS2005.

void CDTEAddIn::SinkEvents(AddInDO::ext_ConnectMode nMode)
{
  if (AddInDO::ext_cm_AfterStartup == nMode || // Loaded after app startup
      AddInDO::ext_cm_Startup      == nMode)   // Loaded on app startup
  {
    switch (m_nHost)
    {
    case Host_VS2003:
    case Host_VS2005:
    {
      CComPtr<EnvDTE::Events> pEvents;
      HRESULT hr = m_pApp->get_Events(&pEvents);
      if (FAILED(hr)) throw _com_error(hr);

      hr = pEvents->get_WindowEvents(NULL, &m_pWinEvents);
      if (SUCCEEDED(hr))
      {
        m_objWindowEvents.DispEventAdvise(m_pWinEvents);
      }
      break;
    }
    default:
      ATLTRACE2(atlTraceHosting, 0, "CoDTEAddIn does not sink events "
                "for host %d!\n", m_nHost);
    }
  }

} // End CDTEAddIn::SinkEvents.

void CDTEAddIn::UnSinkEvents(AddInDO::ext_DisconnectMode /*nMode*/)
{
  switch (m_nHost)
  {
  case Host_VS2003:
  case Host_VS2005:
    if (m_pWinEvents)
    {
      m_objWindowEvents.DispEventUnadvise(m_pWinEvents);
      m_pWinEvents = NULL;
    }
    break;
  }
} // End CDTEAddIn::UnSinkEvents.

// Local Variables:
// fill-column: 72
// indent-tabs-mode: nil
// show-trailing-whitespace: t
// End:

// DTEAddIn.cpp ends here.
